﻿using System;
using System.Reflection;
using System.Reflection.Emit;

namespace Rsdn.SmartApp
{
	/// <summary>
	/// Хелперный класс для работы с сервисами.
	/// </summary>
	public static partial class ServicesHelper
	{
		/// <summary>
		/// Возвращает затребованный сервис, или выбрасывает исключение
		/// <see cref="ServiceNotFoundException"/>, если сервис не найден.
		/// </summary>
		public static T GetRequiredService<T>(this IServiceProvider provider)
			 where T : class
		{
			T svc = provider.GetService<T>();
			if (svc == null)
				throw new ServiceNotFoundException(typeof (T));
			return svc;
		}

		#region Reflected GetService()
		private static readonly MethodInfo _getServiceMethod =
			typeof(IServiceProvider).GetMethod("GetService");
		private static readonly ElementsCache<Type, ServiceGetter> _getters =
			new ElementsCache<Type, ServiceGetter>(CreateGetter);

		/// <summary>
		/// Возвращает затребованный сервис по указанному типу контракта, или выбрасывает исключение
		/// <see cref="ServiceNotFoundException"/>, если сервис не найден.
		/// </summary>
		public static object GetRequiredService(this IServiceProvider provider, Type serviceType)
		{
			object svc = GetService(provider, serviceType);
			if (svc == null)
				throw new ServiceNotFoundException(serviceType);
			return svc;
		}

		/// <summary>
		/// Возвращает сервис по указанному типу контракта.
		/// </summary>
		public static object GetService(this IServiceProvider provider, Type serviceType)
		{
			return _getters.Get(serviceType)(provider);
		}

		private static ServiceGetter CreateGetter(Type serviceType)
		{
			var method = new DynamicMethod("Getter" + serviceType.FullName,
				typeof(object),
				new Type[] { typeof(IServiceProvider) },
				typeof (ServicesHelper),
				true);

			ILGenerator ilGen = method.GetILGenerator();
			ilGen.Emit(OpCodes.Ldarg_0);
			ilGen.Emit(OpCodes.Callvirt, _getServiceMethod.MakeGenericMethod(serviceType));
			ilGen.Emit(OpCodes.Ret);

			return (ServiceGetter)method.CreateDelegate(typeof(ServiceGetter));
		}

		private delegate object ServiceGetter(IServiceProvider provider);
		#endregion

		#region Reflected Publish()
		/// <summary>
		/// Публикует экземпляр сервиса.
		/// </summary>
		public static IServiceCookie Publish(this IServicePublisher publisher, Type serviceType,
			object serviceInstance)
		{
			return ReflectedPublicator.Publish(publisher, serviceType, serviceInstance);
		}

		/// <summary>
		/// Публикует сервис с отложенной инициализацией.
		/// </summary>
		public static IServiceCookie Publish(this IServicePublisher publisher, Type serviceType,
			ServiceCreator<object> serviceCreator)
		{
			return ReflectedPublicator.Publish(publisher, serviceType, serviceCreator);
		}
		#endregion

		/// <summary>
		/// Присваивает экземпляры сервисов специально размеченным полям.
		/// </summary>
		public static void AssignServices(this object instance, IServiceProvider provider)
		{
			AssignHelper.Assign(instance, provider);
		}
	}
}
